package com.declum.naduvar.locking.handler;

import java.io.IOException;

import org.apache.log4j.Logger;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.TransportType;
import org.apache.mina.transport.socket.nio.SocketSessionConfig;

import com.declum.naduvar.locking.db.LockDB;
import com.declum.naduvar.locking.db.LockException;

/**
 * This class implement the handler for the Naduvar Server which maintains the
 * locking service in the network.
 * 
 * For more detailed information, please read the comments above the methods and
 * fields
 * 
 * @author clement
 */
public class LockingHandler extends IoHandlerAdapter {

	/**
	 * Constants for describing the Commands
	 */
	protected static final String LEASE_LOCK = "leaselock";

	protected static final String RELEASE_LOCK = "releaselock";
	protected static final String RENEW_LOCK = "renewlock";
	/**
	 * Object for {@link LockDB}
	 */
	protected LockDB lockdb;

	/**
	 * Logger object for this Class and its children
	 */
	protected Logger logger;

	/**
	 * Constructor that initialize the LockDB and Logger
	 */
	public LockingHandler() {
		/**
		 * Initialize the LockDB
		 */
		this.lockdb = new LockDB();

		/**
		 * Initialize the Logger
		 */
		this.logger = Logger.getLogger(LockingHandler.class);
	}

	/**
	 * If any exception occurred while handling the Client's request, this
	 * method will be called. Here we are just closing the connection. In
	 * future, we can add some processing there
	 */
	@Override
	public void exceptionCaught(IoSession session, Throwable t)
			throws Exception {
		/**
		 * Print to CONSOLE
		 */
		t.printStackTrace();

		/**
		 * Close client session, as some exception occurred
		 */
		session.close();
	}

	public void init() {
		// Do some check
	}

	/**
	 * When ever a line is received, this method is called, as we are using Line
	 * Encoder/Decorder.
	 * 
	 * So for receiving of each line, we are just paring them as header/value.
	 * Once all required headers are received(will get his from RequestHeader
	 * object's isComplete() method), we start processing the requests
	 * 
	 * There are three kinds of Commands identified. They are Lease_Lock,
	 * Renew_lock, and Release_Lock. Based on these values which got from the
	 * Client's request, we start processing.
	 * 
	 * ResponseHeader is used to send the response to the Client
	 */
	@Override
	public void messageReceived(IoSession session, Object msg) throws Exception {
		try {
			/**
			 * We cannot maintain a object state if we are creating it inside
			 * this method. Because, this method will be triggered when ever
			 * client is sending the data. So Usually we create a State
			 * object(RequestHeader) in the SessionCreate method and we will be
			 * accessing it whenever we need through out the session for the
			 * current client
			 */

			RequestHeader header = (RequestHeader) session
					.getAttribute("header");
			header.parse(msg.toString());
			if (header.isComplete()) {

				String type = header.getOperationType().toLowerCase();
				ResponseHeader response = new ResponseHeader();
				try {

					if (type.equals(LockingHandler.LEASE_LOCK)) {
						this.logger.debug("lock request for "
								+ header.getLockPath());
						/**
						 * Acquire the lock from LockDB
						 */
						this.lockdb.getLock(header.getLockPath());

						response.setStatus("success");
						response.setReason("lock acquired for "
								+ header.getLockPath());
						this.logger.debug("lock acquired for "
								+ header.getLockPath());
					} else if (type.equals(LockingHandler.RENEW_LOCK)) {
						this.logger.debug("renew lock request for "
								+ header.getLockPath());
						/**
						 * Renew the lock from LockDB
						 */
						this.lockdb.renewLock(header.getLockPath(), header
								.getLockKey());

						response.setStatus("success");
						response.setReason("lock renewed for "
								+ header.getLockPath());
						this.logger.debug("lock renewed for "
								+ header.getLockPath());
					} else if (type.equals(LockingHandler.RELEASE_LOCK)) {
						this.logger.debug("release lock request for "
								+ header.getLockPath());
						/**
						 * Release the lock from LockDB
						 */
						this.lockdb.releaseLock(header.getLockPath(), header
								.getLockKey());

						response.setStatus("success");
						response.setReason("lock released for "
								+ header.getLockPath());
						this.logger.debug("lock released for "
								+ header.getLockPath());
					} else {
						/**
						 * If none of the above commends satisfied for the
						 * request, then just say good bye
						 */
						response.setStatus("failed");
						response.setReason("Operation not supported");
						this.logger.debug("Operation '" + type
								+ "' not supported");
					}
				} catch (LockException e) {
					/**
					 * If any exception, the definitely something went wrong
					 */
					response.setStatus("failed");
					response.setReason(e.getMessage());
					this.logger.debug(e.getMessage());
				}
				session.write(response.toString());
				session.close();
			}
		} catch (Exception e) {
			/**
			 * Something went terribly wrong
			 */
			e.printStackTrace();
			this.logger.error(e.getMessage());
			session.write(e.getMessage());
		}
	}

	/**
	 * This method will be called once the client is accepted. It does the
	 * object creation and setting configuration information for this client
	 * session
	 */
	@Override
	public void sessionCreated(IoSession session) throws Exception {
		this.logger.debug("Session created");
		/**
		 * Here we are creating the object of RequestHeader. Because it has to
		 * maintains the state between method calls.
		 */
		session.setAttribute("header", new RequestHeader());

		/**
		 * I am really not knowing why this is set here. But sure one day i will
		 * understand. :-)
		 */
		if (session.getTransportType() == TransportType.SOCKET) {
			((SocketSessionConfig) session.getConfig())
					.setReceiveBufferSize(2048);
		}

		/**
		 * Set idle time
		 */
		session.setIdleTime(IdleStatus.BOTH_IDLE, 10);
	}

	/**
	 * Internally sets the lease interval for locks
	 * 
	 * @param defaultLeaseInterval
	 * @throws IOException
	 */
	public void setLeaseInterval(int defaultLeaseInterval) throws IOException {
		this.lockdb.setLeaseInterval(defaultLeaseInterval);
	}

	/**
	 * Internally specify under with the log for lock
	 * creation/modification/deletion has to be persistently stored
	 * 
	 * @param workingDirectory
	 * @throws Exception
	 */
	public void setWorkingDirectory(String workingDirectory) throws Exception {
		this.lockdb.setWorkingDirectory(workingDirectory);

	}
}
